# -*- coding: utf-8 -*-

"""
--------------------------------------------
project: wechat_official_SCF
author: 子不语
date: 2024/11/20
contact: 【公众号】思维兵工厂
description: 处理微信消息

get方法处理get请求，主要是微信验证接口有效性;
post方法处理post请求;
--------------------------------------------
"""

import os
import json
import hashlib
import traceback
import xmltodict
from flask import Request
from datetime import datetime
from typing import Optional, Dict

from .types import ConfigData
from .constant import parent_dir
from core.utils.storage import Qiniu
from .handle_post import PostHandler
from .config import pro_logger, config


class RequestHandler(object):

    def __init__(self) -> None:

        self.config: Optional[ConfigData] = None

    @staticmethod
    def record_failed_request(request_msg: Dict, traceback_msg: str) -> None:
        """
        处理请求处理失败的记录:
            暂时考虑将错误信息写成文件，上传到七牛云
        :param request_msg: 微信发送的请求信息，已解析成Python的字典格式；
        :param traceback_msg: 错误的堆栈信息
        :return:
        """

        if not config.record_failed_request:
            config.is_debug and pro_logger.info('未开启记录错误信息的功能')
            return

        if not config.qiniu_config.is_valid():
            config.is_debug and pro_logger.error('未配置七牛云参数，无法记录错误信息')
            return

        qiniu_handle = Qiniu(
            access_key=config.qiniu_config.access_key,
            secret_key=config.qiniu_config.secret_key,
            bucket_name=config.qiniu_config.bucket_name,
        )

        time_str = datetime.now().strftime('%Y%m%d_%H%M%S')
        file_name = f'failed_{time_str}.md'
        local_file_path = os.path.join(parent_dir, file_name)

        with open(local_file_path, 'w', encoding='utf-8') as f:
            f.write(f'# 处理失败记录_{time_str}\n\n')
            f.write(f'### 请求参数\n\n```json\n{json.dumps(request_msg, ensure_ascii=False, indent=4)}\n```\n\n')
            f.write(f'### 错误信息\n\n```\n{traceback_msg}\n```\n\n')

        remote_file_path = f"failed_record/{file_name}"
        result = qiniu_handle.upload_file(
            local_file_path=local_file_path,
            remote_file_path=remote_file_path
        )
        result and config.is_debug and pro_logger.info(f'错误信息文件已上传至七牛云，文件路径为【{remote_file_path}】')
        not result and config.is_debug and pro_logger.error(f'错误信息文件上传至七牛云失败')

    def authenticate(self, query_data: dict) -> bool:
        """根据请求数据，验证是否为微信服务器发送的消息"""

        # 获取微信传递的参数
        signature = query_data.get('signature', '')
        timestamp = query_data.get('timestamp', '')
        nonce = query_data.get('nonce', '')

        if not signature or not timestamp or not nonce:
            return False

        tmp_list = [self.config.wechat_config.wechat_token, timestamp, nonce]
        tmp_list.sort()

        tmp_str = "".join(tmp_list)
        hashcode = hashlib.sha1(tmp_str.encode('utf8')).hexdigest()

        if hashcode == signature:
            self.config.is_debug and pro_logger.info('经过验证，确定为微信服务器信息')
            return True
        else:
            self.config.is_debug and config.is_debug and pro_logger.error('经过验证，该请求不是微信服务器信息')
            return False

    def get(self, request: Request) -> str:
        """处理get请求"""

        echo_str = request.args.get('echostr')
        if not echo_str:
            self.config.is_debug and pro_logger.info("get请求中没有echostr参数，并非微信服务器请求")
            return "This get request is not for authenticated."

        if self.authenticate(request.args):
            return echo_str

        return 'authenticate failed!'

    def post(self, request: Request) -> str:
        """处理post请求"""

        # 先验证是否为微信服务器发送的信息
        if not self.authenticate(request.args):
            return "not wechat post request"

        # 获取请求携带的参数
        xml_dict: Dict = xmltodict.parse(request.data.decode('utf-8')).get('xml', {})

        msg_type = xml_dict.get('MsgType')  # 获取本次消息的MsgType

        # 测试模式时打印每次请求的信息
        if self.config.is_debug:
            print(xml_dict)
            pro_logger.info(f"用户发送的消息类型是【{msg_type}】")

        handler = PostHandler(xml_dict)

        try:

            if handler.check_keyword():
                return handler.real_reply_message

            result, continue_flag = handler.check_message()

            if result:
                return handler.real_reply_message

            if continue_flag:
                handle_method = getattr(handler, msg_type, 'unknown')
                handle_method()

                # 将本次交互信息写入数据库
                handler.save_message()
                return handler.real_reply_message
        except:

            # 日志记录
            config.is_debug and pro_logger.error("出现未知错误", exc_info=True)

            # 记录错误信息
            traceback_msg = traceback.format_exc()
            self.record_failed_request(xml_dict, traceback_msg)

            return handler.make_reply_text("服务器内部错误，请联系管理员！")
        finally:
            handler.close_database()
